package com.example.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mapper.GoodsMapper;
import com.example.po.GoodsPO;
import com.example.service.DbConcurrencySafe;
import com.example.service.GoodsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Resource;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
/*
@description:
@ClassName GoodsServiceImpl
@author chen
@create 2025-06-10 17:22
@Version 1.0
*/
@Service
@Slf4j
public class GoodsServiceImpl  extends ServiceImpl<GoodsMapper, GoodsPO> implements GoodsService
{

    @Resource
    private GoodsMapper goodsMapper;

    @Resource
    private TransactionTemplate transactionTemplate;

    @Resource
    private DbConcurrencySafe dbConcurrencySafe;


    /*
     * @==============History===============<br/>;
     * @Description //   1、第一种解决超卖的方法：通过update中携带条件判断解决超卖问题
     * @Date 2025/6/10
     */
    @Override
    public void placeOrder1() throws InterruptedException
    {
        Function<String, Integer> reduceStock = (String goodsId) -> goodsMapper.placeOrder1(goodsId, 1);
        // 模拟100人秒杀
        this.concurrentPlaceOrderMock("方案1", reduceStock);
    }

    @Override
    public void placeOrder2() throws InterruptedException
    {
        // 扣减库存的方法，返回值为1表示扣减库存成功，0表示失败
        Function<String, Integer> reduceStock = (String goodsId) ->
        {
            // 1、先根据id查询商品id
            GoodsPO goodsPO = this.getById(goodsId);

            // 2、判断库存是否==0，则直接返回失败
            if (goodsPO.getNum() == 0)
            {
                return 0;
            }
            // 3.库存看起来够，但是并发的时候可能就不够了，下面带版本号更新库存，判断影响行数，update 为1表示成功，0表示扣减库存失败
            return goodsMapper.placeOrder2(goodsId, 1, goodsPO.getVersion());
        };

        this.concurrentPlaceOrderMock("方案2", reduceStock);
    }

    @Override
    public void placeOrder3() throws InterruptedException
    {
        Function<String, Integer> reduceStock = (String goodsId) ->
        {
            // 1、根据商品id获商品
            GoodsPO updateBeforeGoods = this.getById(goodsId);
            // 2、判断库存是否够
            if (updateBeforeGoods.getNum() == 0)
            {
                return 0;
            }
            return transactionTemplate.execute(status ->
            {
                // 3、执行更新扣减库存
                this.goodsMapper.placeOrder3(goodsId, 1);
                // 4、修改数据完成后，查出来看一下，和期望的结果是不是一致的，如果是，表示成功，否则失败
                GoodsPO updateAfterGoods = this.getById(goodsId);
                if(updateBeforeGoods.getNum() - 1 != updateAfterGoods.getNum())
                {
                    //设置事务回滚
                    status.setRollbackOnly();
                    return 0;
                }
                else
                {
                    return 1;
                }
            });
        };
        // 模拟100人秒杀
        this.concurrentPlaceOrderMock("方案3", reduceStock);
    }

    @Override
    public void placeOrder4() throws InterruptedException
    {
        // 扣减库存的方法，返回值为1表示扣减库存成功，0表示失败
        Function<String, Integer> reduceStock = (String goodsId) ->
        {
            try
            {
                //使用 dbConcurrencySafe.exec 包住需要并发操作的数据，可以确保数据修改的安全性
                return this.dbConcurrencySafe.exec(GoodsPO.class, goodsId, () ->
                {
                    // 1、根据商品id获商品
                    GoodsPO goodsPO = this.getById(goodsId);
                    // 2、判断库存是否够
                    if (goodsPO.getNum() == 0)
                    {
                        return 0;
                    }
                    // 3、执行更新扣减库存
                    this.goodsMapper.placeOrder3(goodsId, 1);
                    return 1;
                });
            }
            catch (Exception e)
            {
                return 0;
            }
        };
        // 模拟100人秒杀
        this.concurrentPlaceOrderMock("方案4", reduceStock);
    }


    /*
     * @param method
     * @param fun 扣减库存的函数，fun函数的参数为商品id，返回值：1：表示抢购成功，0：表示抢购失败
     * @==============History===============<br/>;
     * @Description //
     * @Date 2025/6/10
     */
    private void concurrentPlaceOrderMock(String method, Function<String, Integer> fun) throws InterruptedException
    {
        // 1、初始化一条商品记录 [商品Id:1,名称：iphone、库存：10]
        String goodsId = "1", goodsName = "iphone";
        // 库存10个
        int num = 10;

        GoodsPO goodsStart = this.initTestData(goodsId, goodsName, num);
        log.info("【{}】开始执行秒杀任务", method);
        // 2、创建线程池，大小为100，模拟100个线程并发下单
        int concurrentNum = 100;
        ExecutorService executorService = Executors.newFixedThreadPool(concurrentNum);

        CountDownLatch countDownLatch = new CountDownLatch(concurrentNum);

        AtomicInteger successNum = new AtomicInteger(0);
        AtomicInteger failNum = new AtomicInteger(0);

        for (int i = 0; i < concurrentNum; i++)
        {
            executorService.execute(() ->
            {
                try
                {
                    // 调用抢购的函数，update表示抢购的结果，1：表示抢购成功，0：抢购失败
                    int update = fun.apply(goodsId);
                    // update=0,表示更新失败，否则表示更新成功
                    if (update == 0)
                    {
                        // 抢购失败，失败次数+1，业务上在这里可能要做一些事情（如让事务回滚，那么可以在此抛出一个异常，spring事务会自动回滚）
                        failNum.incrementAndGet();
                    }
                    else
                    {
                        //更新成功，成功次数+1
                        successNum.incrementAndGet();
                    }
                }
                finally
                {
                    countDownLatch.countDown();
                }
            });
        }

        // 调用下面的方法，等待上面线程池中的任务都执行完毕后，才会继续向下走
        countDownLatch.await();

        // 模拟100人抢购结束，获取抢购完毕后商品的信息
        GoodsPO goodsEnd = this.getById(goodsId);


        //抢购完毕，输出日志，方便看效果，输出商品目前的信息，下单成功的数量，失败的数量
        System.out.printf("===========================解决超卖，%s 开始执行=======================================%n", method);

        System.out.printf("模拟 %s 人进行抢购%n", concurrentNum);
        System.out.println("抢购结束啦............\n");
        System.out.printf("抢购前，商品库存：%s%n", goodsStart.getNum());
        System.out.printf("抢购后，商品库存：%s%n", goodsEnd.getNum());
        System.out.printf("下单成功人数：%s%n", successNum.get());
        System.out.printf("下单失败人数：%s%n", failNum.get());

        System.out.printf("===========================解决超卖，%s 执行结束=======================================%n", method);
    }


    private GoodsPO initTestData(String goodsId, String goodsName, int num)
    {
        // 清理下这个商品
        this.removeById(goodsId);
        // 重新插入，方便测试
        GoodsPO goodsPO = new GoodsPO();
        goodsPO.setGoodsId(goodsId);
        goodsPO.setGoodsName(goodsName);
        goodsPO.setNum(num);
        goodsPO.setVersion(0L);
        this.save(goodsPO);
        return goodsPO;
    }
}
